/*
 * $Id: BufferPool.cc,v 1.2 2005-04-24 08:17:08 bacon Exp $
 */

#include <xpp/bas/BufferPool.h>
#include <xp/bas/assert.h>

namespace xpp
{
	namespace bas
	{

		BufferPool::BufferPool (xp_size_t size)
		{
			blocks = XP_NULL;
			if (size < MIN_BLOCK_SIZE) size = MIN_BLOCK_SIZE;
			block_size = size;
		}

		BufferPool::~BufferPool ()
		{
			dispose ();
		}

		void* BufferPool::allocate (xp_size_t size)
		{
			size = size + xp_sizeof(Chain);

			Block* block = blocks;
			while (block != XP_NULL) {
				void* tmp = allocate (block, size);
				if (tmp != XP_NULL) return tmp;
				block = block->next;
			}

			block = add_block ((size > block_size)? size: block_size);
			return allocate (block, size);
		}

		void* BufferPool::allocate (Block* block, xp_size_t size)
		{
			//if (size < xp_sizeof(Chain)) size = xp_sizeof(Chain);
			Chain* ptr = block->free;

			Chain* prev = XP_NULL;
			while (ptr != XP_NULL) {
				if (ptr->size >= size) { 
					// first-fit rather than best-fit
					if (ptr->size - size >= xp_sizeof(Chain)) {
						Chain* tmp = ptr;
						tmp = (Chain*)((xp_byte_t*)ptr + size);
						tmp->size = ptr->size - size;
						tmp->owner = block;
						tmp->next = ptr->next;

						ptr->size = size;

						if (prev != XP_NULL) prev->next = tmp;
						else block->free = tmp;
					}
					else {
						if (prev != XP_NULL) prev->next = ptr->next;
						else block->free = ptr->next;
					}
					return (void*)&ptr->next;
				}

				prev = ptr;
				ptr = ptr->next;
			}

			return XP_NULL;
		}

		void BufferPool::dispose (void* ptr)
		{
			Chain* mem = (Chain*)((xp_byte_t*)ptr - xp_offsetof(Chain,next));
			Block* block = mem->owner;

			//printf (
			//	"trying to dispose %u(%u) in %u\n", 
			//	ptr, mem->size, block);

			Chain* free = block->free;
			Chain* prev = XP_NULL;

			while (free != XP_NULL && free < mem) {
				prev = free;
				free = free->next;
			}

			if (free != XP_NULL && 
			    (Chain*)((xp_byte_t*)mem + mem->size) == free) {
				mem->size += free->size;
				mem->next = free->next;
				//printf ("merging...\n");
			}
			else {
				mem->next = free;
				//printf ("not merging...\n");
			}

			if (prev != XP_NULL) {
				//printf (">> %u: %u: %u: %u\n", prev, (Chain*)((xp_byte_t*)prev + prev->size), mem, ptr);
				if ((Chain*)((xp_byte_t*)prev + prev->size) == mem) {
					prev->size += mem->size;
					prev->next = mem->next;
					//printf ("merging 2...\n");
				}
				else {
					prev->next = mem;
					//printf ("not merging 2...\n");
				}
			}
			else {
				block->free = mem;	
				//printf ("not merging 3... \n");
			}

			Chain* c = block->free;
			while (c) {
				//printf ("-- %u\n", c);
				c = c->next;
			}
		}

		void BufferPool::dispose ()
		{
			Block* block = blocks;
			while (block != XP_NULL) {
				Block* next = block->next;
				::delete[] block;
				block = next;
			}

			blocks = XP_NULL;
		}

		BufferPool::Block* BufferPool::add_block (xp_size_t size)
		{
			xp_assert (size > xp_sizeof(Chain));

			Block* block = (Block*)::new xp_byte_t[xp_sizeof(Block) + size];

			block->free = (Chain*)(block + 1);
			block->free->size  = size;
			block->free->owner = block;
			block->free->next = XP_NULL;

			block->end = (Block*)((xp_byte_t*)block + xp_sizeof(Block) + size);

			block->next = blocks;
			blocks = block;

			//printf (">> adding block :%u\n", block);
			return block;
		}

	}
}
